import filecmp
import os

import table_ast
import table_lexer
import table_parser

class CxxMessageHandler:
    def __init__(self, msgdir, outdir, objectType, objectName,
                 handlerName, msgPrefixs, msgTags, msgMax, extArgs = None):
        self.msgdir, self.outdir = msgdir, outdir
        self.objectType, self.objectName = objectType, objectName
        self.handlerName, self.msgPrefixs = handlerName, msgPrefixs
        self.msgTags, self.msgMax, self.extArgs = msgTags, msgMax, extArgs

def to_cxx_files(indir, outdir, msgHandlers):
    def check_replace_file(filename):
        if not os.path.exists(filename[:-1]) or \
           not filecmp.cmp(filename, filename[:-1], False):
            os.replace(filename, filename[:-1])
        else:
            os.remove(filename)

    def parse_ast(infile):
        lexer = table_lexer.TableLexer()
        lexer.parse(infile)
        parser = table_parser.TableParser()
        return parser.parse(lexer)

    def parse_blanks(infile):
        blanks = []
        with open(infile, 'r') as fo:
            for i, line in enumerate(fo.readlines()):
                if line.isspace():
                    blanks.append(i + 1)
        return blanks

    def generate_header_file(indir, outdir, filename):
        filepart = os.path.splitext(filename)[0]
        outpathpart = os.path.join(outdir, filepart)
        infile = os.path.abspath(os.path.join(indir, filename))
        print('cxx files %s ...' % infile)
        root, blanks = parse_ast(infile), parse_blanks(infile)
        outfile, prefix = outpathpart+'.h~', '#pragma once\n\n'
        GeneratorH(blanks).Generate(root, outfile, prefix)
        check_replace_file(outfile)
        return filepart, root

    def generate_message_handler(rootFileList, msgHandler):
        print('cxx message handler %s ...' % msgHandler.handlerName)
        if not os.path.exists(msgHandler.outdir):
            os.mkdir(msgHandler.outdir)
        outfile = os.path.join(msgHandler.outdir, msgHandler.handlerName+'.h~')
        GeneratorMHH().Generate(outfile, rootFileList, msgHandler)
        check_replace_file(outfile)
        outfile = os.path.join(msgHandler.outdir, msgHandler.handlerName+'.cpp~')
        GeneratorMHCPP().Generate(outfile, rootFileList, msgHandler)
        check_replace_file(outfile)

    if os.path.exists(indir):
        if not os.path.exists(outdir):
            os.mkdir(outdir)
        rootFileList = {}
        for filename in os.listdir(indir):
            name, root = generate_header_file(indir, outdir, filename)
            rootFileList[name] = root
        for msgHandler in msgHandlers:
            generate_message_handler(rootFileList, msgHandler)

class GeneratorH:
    def __init__(self, blanks):
        self.blanks, self.i = blanks, 0
        self.lineno = 0

    def Generate(self, root, outfile, prefix = ''):
        with open(outfile, 'w') as self.fo:
            self.fo.write(prefix)
            for entity in root.externalDeclarations:
                self.__generateentity(entity, 0)

    def __generateentity(self, entity, level):
        if isinstance(entity, table_ast.EnumDefinition):
            return self.__generateenumblock(entity, level)

        if isinstance(entity, table_ast.EnumDeclaration):
            return self.__generateenummember(entity, level)

        if isinstance(entity, table_ast.Preprocessor):
            return self.__writeline2file(level,
                entity.text.value.rstrip(), entity.text.lineno)

        if isinstance(entity, table_ast.Comment):
            return self.__writeline2file(level,
                entity.text.value.rstrip(), entity.text.lineno,
                canInline = True, strSpacer = '  ')

    def __generateenumblock(self, entity, level):
        self.__writeline2file(level, 'enum ' + entity.name.value,
            entity.name.lineno)
        self.__generateblockmember(entity, level)

    def __generateblockmember(self, entity, level):
        self.__writeline2file(level, '{')
        for member in entity.declarationList.declarationList:
            self.__generateentity(member, level + 1)
        self.__writeline2file(level, '};')

    def __generateenummember(self, member, level):
        self.__writeline2file(level, member.memName.value +
            self.__enumValue2formatStr(member.memValue) + ',',
            member.memName.lineno, canInline = True, strSpacer = ' ')

    def __writeline2file(self, level, linedata, lineno = -1, **kwargs):
        while len(self.blanks) > self.i and self.blanks[self.i] < lineno:
            self.fo.write('\n')
            self.i += 1
        if kwargs and kwargs['canInline'] and self.lineno == lineno:
            self.fo.seek(self.fo.tell() - len(os.linesep))
            self.fo.write(kwargs['strSpacer'])
        elif level != 0:
            self.fo.write('\t' * level)
        self.fo.write(linedata)
        self.fo.write('\n')
        if lineno != -1:
            self.lineno = lineno

    @classmethod
    def __enumValue2formatStr(cls, node):
        return (' = ' + (cls.__to_cxx_ns(node) if
            isinstance(node, table_ast.NsIdList) else node)) if node else ''

    @staticmethod
    def __to_cxx_ns(node):
        return '::'.join(ns.value for ns in node.idList)

class GeneratorMHBase:
    class MsgInfo:
        def __init__(self, rpc, *parts):
            assert len(parts) == 2, 'msg part number must equal 2.'
            self.rpc, self.parts = rpc, parts

    def _write_include_message_file(self, msgdir, filename):
        self.fo.write('#include "%s%s%s.h"\n' % (msgdir or '',
            msgdir and msgdir[-1] != '/' and '/' or '', filename))

    @staticmethod
    def _beautify_message_name(msgName):
        parts = [part.title() for part in msgName.split('_')]
        return 'Handle' + ''.join(parts[1 if len(parts) > 1 else 0 :])

    @classmethod
    def _filter_message(cls, rootFileList, msgPrefixs, msgTags):
        files, msgs = set(), []
        for name, root in rootFileList.items():
            for entity in root.externalDeclarations:
                if not isinstance(entity, table_ast.EnumDefinition):
                    continue
                for i, member in enumerate(entity.declarationList.declarationList):
                    if not isinstance(member, table_ast.EnumDeclaration):
                        continue
                    tags = cls.__get_message_tags(entity.
                        declarationList.declarationList[i+1:], member.memName.lineno)
                    if not cls.__check_message_available(
                        member.memName.value, tags, msgPrefixs, msgTags):
                        continue
                    files.add(name)
                    msgs.append(cls.MsgInfo(tags and 'rpc' in tags or False,
                        entity.name.value, member.memName.value))
        return sorted(files), msgs

    @staticmethod
    def __get_message_tags(declarationList, lineno):
        for member in declarationList:
            if isinstance(member, table_ast.EnumDeclaration):
                if member.memName.lineno <= lineno:
                    continue
            if isinstance(member, table_ast.Comment):
                if member.text.lineno == lineno:
                    try:
                        return member.text.tags
                    except AttributeError as e:
                        pass
            return

    @staticmethod
    def __check_message_available(memName, tags, msgPrefixs, msgTags):
        if tags and (len(tags) > 1 or tags[0] != 'rpc'):
            return 'ingore' not in tags and \
                next((True for msgTag in msgTags if msgTag in tags), False)
        else:
            return next((True for msgPrefix in \
                msgPrefixs if memName.startswith(msgPrefix + '_')), False)

class GeneratorMHH(GeneratorMHBase):
    def Generate(self, outfile, rootFileList, msgHandler):
        with open(outfile, 'w') as self.fo:
            msgMaxFile = self.__find_message_max_file(rootFileList, msgHandler.msgMax)
            _, msgs = self._filter_message(
                rootFileList, msgHandler.msgPrefixs, msgHandler.msgTags)
            self.fo.write('#pragma once\n\n')
            self.fo.write('#include "Singleton.h"\n#include "MessageHandler.h"\n')
            self._write_include_message_file(msgHandler.msgdir, msgMaxFile)
            self.fo.write('\n')
            self.fo.write('class %s;\n\n' % msgHandler.objectType)
            self.fo.write(('class %s :\n\tpublic MessageHandler<%s, %s, %s%s>,' +
                           '\n\tpublic Singleton<%s>\n') %
                (msgHandler.handlerName, msgHandler.handlerName, msgHandler.objectType,
                 msgHandler.msgMax, self.__to_cxx_ext_types(msgHandler.extArgs),
                 msgHandler.handlerName))
            self.fo.write('{\n')
            self.fo.write('public:\n')
            self.fo.write('\t%s();\n\tvirtual ~%s();\n' %
                (msgHandler.handlerName, msgHandler.handlerName))
            self.fo.write('private:\n')
            for msg in msgs:
                self.__write_message_handler(msg, msgHandler)
            self.fo.write('};\n\n')
            self.fo.write('#define s%s (*%s::instance())\n' %
                (msgHandler.handlerName, msgHandler.handlerName))

    def __write_message_handler(self, msg, msgHandler):
        if not msg.rpc:
            self.fo.write('\tint %s(%s *%s, INetPacket &pck%s);\n' %
                (self._beautify_message_name(msg.parts[1]),
                 msgHandler.objectType, msgHandler.objectName,
                 self.__to_cxx_ext_params(msgHandler.extArgs)))
        else:
            self.fo.write(('\tint %s(%s *%s, INetPacket &pck, ' +
                           'const RPCActor::RequestMetaInfo &info%s);\n') %
                (self._beautify_message_name(msg.parts[1]),
                 msgHandler.objectType, msgHandler.objectName,
                 self.__to_cxx_ext_params(msgHandler.extArgs)))

    @staticmethod
    def __find_message_max_file(rootFileList, msgMax):
        msgMaxParts = [part.strip() for part in msgMax.split('::')]
        assert len(msgMaxParts) == 2, 'msgMax part number must equal 2.'
        for name, root in rootFileList.items():
            for entity in root.externalDeclarations:
                if not isinstance(entity, table_ast.EnumDefinition):
                    continue
                if entity.name.value != msgMaxParts[0]:
                    continue
                for member in entity.declarationList.declarationList:
                    if not isinstance(member, table_ast.EnumDeclaration):
                        continue
                    if member.memName.value != msgMaxParts[1]:
                        continue
                    return name

    @staticmethod
    def __to_cxx_ext_types(extArgs):
        return ''.join(', %s' % extArgs[i] for i in range(0, len(extArgs), 2)) \
            if extArgs else ''

    @staticmethod
    def __to_cxx_ext_params(extArgs):
        return ''.join(', %s %s' % extArgs[i:i+2] for i in range(0, len(extArgs), 2)) \
            if extArgs else ''

class GeneratorMHCPP(GeneratorMHBase):
    def Generate(self, outfile, rootFileList, msgHandler):
        with open(outfile, 'w') as self.fo:
            files, msgs = self._filter_message(
                rootFileList, msgHandler.msgPrefixs, msgHandler.msgTags)
            self.fo.write('#include "preHeader.h"\n')
            self.fo.write('#include "%s.h"\n' % msgHandler.handlerName)
            for filename in files:
                self._write_include_message_file(msgHandler.msgdir, filename)
            self.fo.write('\n')
            self.fo.write('%s::%s()\n' % (msgHandler.handlerName, msgHandler.handlerName))
            self.fo.write('{\n')
            for msg in msgs:
                self.__write_message_handler(msg, msgHandler)
            self.fo.write('};\n\n')
            self.fo.write('%s::~%s()\n{\n}\n' % (msgHandler.handlerName, msgHandler.handlerName))

    def __write_message_handler(self, msg, msgHandler):
        if not msg.rpc:
            self.fo.write('\thandlers_[%s::%s] = &%s::%s;\n' %
                (msg.parts[0], msg.parts[1], msgHandler.handlerName,
                 self._beautify_message_name(msg.parts[1])))
        else:
            self.fo.write('\trpc_handlers_[%s::%s] = &%s::%s;\n' %
                (msg.parts[0], msg.parts[1], msgHandler.handlerName,
                 self._beautify_message_name(msg.parts[1])))
